/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package imagemanip.structures;

import fr.univnantes.alma.arbres.Arbre.ArbreIteratorType;
import fr.univnantes.alma.arbres.Noeud;
import fr.univnantes.alma.arbres.affichage.AffichageLA;
import fr.univnantes.alma.arbres.exceptions.PereRacineException;
import fr.univnantes.alma.arbres.exceptions.PlaceIndisponibleException;
import imagemanip.Main;
import java.awt.Color;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author judu
 */
public class QuadTreeManager {

    private QuadTree qt = new QuadTree();
    private OcTreeManager otm = new OcTreeManager();
    private int longBinaryNumber;
    private int largeur;
    private int hauteur;
    private int profondeur;
    private static int metrique = 5;

    public QuadTreeManager() {
    }

    public OcTreeManager getOtm() {
        return otm;
    }

    public void setOtm(OcTreeManager otm) {
        this.otm = otm;
    }


    

    private void setLongBinaryNumber(int abs, int ord) {
        this.longBinaryNumber = Integer.toBinaryString((abs < ord) ? ord - 1 : abs - 1).length();
    }

    private boolean puissance4(int entier) {
        return (Math.floor(Math.log(entier)/Math.log(4)) == (Math.log(entier)/Math.log(4)));

    }

    /**
     * Indique si un nœud est fusionnable, i.e. si ses 4 fils sont suffisamment rapprochés en terme de distance inter-couleur.
     *
     * @param n un nœud dont les fils sont idéalement des feuilles, et contiennent donc une couleur.
     * @return un booléen.
     */
    public boolean fusionnable(Noeud n){
        try {
            Noeud<Color> fils1 = this.qt.getFilsByPos(n, 1);
            Noeud<Color> fils2 = this.qt.getFilsByPos(n, 2);
            Noeud<Color> fils3 = this.qt.getFilsByPos(n, 3);
            Noeud<Color> fils4 = this.qt.getFilsByPos(n, 4);

            if(fils1 != null && fils2 != null && fils3 != null && fils4 != null){
                boolean b = (ColorEditor.distance4Colors(fils1.getValeur(), fils2.getValeur(), fils3.getValeur(), fils4.getValeur()) <= ColorEditor.getDist());
                //System.out.println("b = "+b);
                return b;
            }
        } catch (PlaceIndisponibleException ex) {
            Logger.getLogger(QuadTreeManager.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NullPointerException ex) {
            Logger.getLogger(QuadTreeManager.class.getName()).log(Level.SEVERE, null, ex);
        }

        return false;
    }


    /**
     * Fusionne les fils d'un nœud, et met le résultat dans le père. Idéalement, les 4 fils du nœud sont des feuilles, et représentent des couleurs
     * Si les fils ne sont pas fusionnable, parce qu'ils ne possèdent pas de valeur, où parce qu'ils sont trop éloignés on ne fait rien.
     * @param n Noeud à fusionner
     */
    public void fusionNoeud(Noeud n){
        if (n != null){
            try {
            Noeud<Color> fils1 = this.qt.getFilsByPos(n, 1);
            Noeud<Color> fils2 = this.qt.getFilsByPos(n, 2);
            Noeud<Color> fils3 = this.qt.getFilsByPos(n, 3);
            Noeud<Color> fils4 = this.qt.getFilsByPos(n, 4);

            //System.out.println("Fusionnable ?");
            if (fusionnable(n)) {

                //System.out.println("Fusion !!");
                //On calcule la nouvelle couleur
                Color nouv = ColorEditor.moyenne4Colors(fils1.getValeur(), fils2.getValeur(), fils3.getValeur(), fils4.getValeur());
                    try {
                        //On supprime n de l'arbre.
                        Noeud p = this.qt.getPere(n);
                        int pos = this.qt.getPosition(n);
                        this.qt.deleteNoeud(n);
                        this.qt.ajoutFilsEnPos(p, pos,nouv);
                    } catch (PereRacineException ex) {
                        Logger.getLogger(QuadTreeManager.class.getName()).log(Level.SEVERE, null, ex);
                    }
            }
            } catch (PlaceIndisponibleException ex) {
                Logger.getLogger(QuadTreeManager.class.getName()).log(Level.SEVERE, null, ex);
            } catch (NullPointerException ex) {
                Logger.getLogger(QuadTreeManager.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        ////System.out.println((new AffichageLA(qt, ArbreIteratorType.DFS)).toString());

    }


    /**
     * Cette méthode compresse l'image jusqu'à qu'il n'y ait plus rien à compresser.
     */
    public void compresse(){

        
        LinkedList<Noeud<Color>> ll = null;
        do{

            Iterator<Noeud<Color>> it = this.qt.iteratorBFS();
            ll = new LinkedList<Noeud<Color>>();

            while(it.hasNext()){
                Noeud<Color> n = it.next();
                if(this.fusionnable(n)){
                    //System.out.println("Nœud fusionnable ? On ajoute.");
                    ll.add(n);
                }
            }

            Iterator<Noeud<Color>> itll = ll.iterator();

            while(itll.hasNext()){
                this.fusionNoeud(itll.next());
            }

        }while(!(ll.isEmpty()));

        //System.out.println((new AffichageLA(qt, ArbreIteratorType.DFS)).toString());

    }


    /**
     * définit la profondeur maximum de l'arbre en fonction des dimensions de l'image.
     */
    private void setProfondeurMax() {

        int ppp2;

        int max = (int) Math.pow(Math.max(this.hauteur, this.largeur),2);
System.out.println("max = "+max);
        if (puissance4(max)) {
            ppp2 = max;
        } else {
            int nb_bits = Integer.toBinaryString(max).length();
System.out.println("nb_bits = "+nb_bits);
            String s = "1";
            for (int i = 0; i < nb_bits; ++i) {
                s = s.concat("0");
            }
            ppp2 = Integer.parseInt(s, 2);
        }
System.out.println("ppp2 = "+ppp2);

        this.profondeur = (int) Math.round(Math.log(ppp2) / Math.log(4));
        //System.out.println("profondeur : "+this.profondeur);
        //System.out.println("ppp2 : "+ppp2);
    }

    public QuadTree getQt() {
        return qt;
    }

    public void setQt(QuadTree qt) {
        this.qt = qt;
    }

    /**
     * Ajoute un pixel de coordonnées (x,y) au quadTree. Crée le "chemin" s'il n'existe pas.
     * @param c Color du pixel
     * @param x Abscisse du pixel
     * @param y Ordonnée du pixel
     * @throws NullPointerException
     * @throws PlaceIndisponibleException
     */
    private void addPixel(Color c, int x, int y) throws NullPointerException, PlaceIndisponibleException {
        String xs = Integer.toBinaryString(x);
        String ys = Integer.toBinaryString(y);

        //On les met à la même taille
        while (ys.length() < this.longBinaryNumber) {
            ys = "0".concat(ys);
        }

        while (xs.length() < this.longBinaryNumber) {
            xs = "0".concat(xs);
        }

        Noeud<Color> pere = this.qt.getRacine();

        for (int i = 0; i < xs.length(); ++i) {
            int num = Integer.parseInt(String.valueOf(xs.charAt(i)).concat(String.valueOf(ys.charAt(i))), 2);
            Color value = null;
            if (i == xs.length() - 1) {
                //On ajoute la couleur avec le Noeud
                value = c;
            }
            Noeud<Color> fils = this.qt.getFilsByPos(pere, num + 1);
            if (fils == null) {
                fils = this.qt.ajoutFilsEnPos(pere, num + 1, value);
            }
            pere = fils;

        }
    }

    /**
     * Importe un tableau de Color de dimension 2 dans un quadtree
     * Stocke aussi la largeur, la hauteur, et la profondeur pour l'export.
     * @param pixels Color[][] - l'image stockée dans un tableau à deux dimensions de Color
     */
    public void importBitMapTable(Color[][] pixels) {


        this.otm.importPaletteImage(pixels);

        this.hauteur = pixels.length;
        this.largeur = pixels[0].length;

        this.setProfondeurMax();
System.out.println("profondeur = "+this.profondeur);
        
        this.setLongBinaryNumber(pixels.length, pixels[0].length);

        for (int y = 0; y < pixels.length; ++y) {
            for (int x = 0; x < pixels[y].length; ++x) {
                try {
                    addPixel(pixels[y][x], x, y);
                } catch (NullPointerException ex) {
                    Logger.getLogger(QuadTreeManager.class.getName()).log(Level.SEVERE, null, ex);
                } catch (PlaceIndisponibleException ex) {
                    Logger.getLogger(QuadTreeManager.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }

    
    /**
     * Cette fonction Retourne une matrice composée des matrices de ses fils.
     * 
     * @param current Noeud courant
     * @param prof Profondeur courante
     * @return Un tableau à deux dimensions de Color, région représentée par current
     */
    private Color[][] boutDeTableau(Noeud<Color> current, int prof) throws PlaceIndisponibleException {

        Integer arite = 4;
        int diffProf = this.profondeur - prof;

        if (current == null) {
            return null;
        } else {
            ////System.out.println("Process de " + current.toString());
            ////System.out.println("profondeur : "+ prof);

            if (current.getValeur() == null) {
                // Si on est à un noeud, on concatène les 4 tableaux de ses fils en un seul.

             // System.out.println("Process fils de " + current.toString());
                
                Color[][] fils0 = boutDeTableau(this.qt.getFilsByPos(current, 1), prof + 1);
                Color[][] fils1 = boutDeTableau(this.qt.getFilsByPos(current, 2), prof + 1);
                Color[][] fils2 = boutDeTableau(this.qt.getFilsByPos(current, 3), prof + 1);
                Color[][] fils3 = boutDeTableau(this.qt.getFilsByPos(current, 4), prof + 1);


                /* Tests, affichage des tableaux*/
                /*//System.out.println("fils0 : ");
                Main.printTableau(fils0);

                //System.out.println("fils1 : ");
                Main.printTableau(fils1);

                //System.out.println("fils2 : ");
                Main.printTableau(fils2);

                //System.out.println("fils3 : ");
                Main.printTableau(fils3);
                /* fin tests */

                /*//System.out.println("Fin Process fils de " + current.toString());

                //System.out.println("Fils0 = "+fils0.toString());*/

                int larFils0 = 0;
                if(fils0 != null) larFils0 = fils0.length;
                int hauFils0 = (fils0 != null && fils0.length > 0 && fils0[0] != null) ? fils0[0].length : 0;

                int larFils1 = (fils1 != null) ? fils1.length : 0;
                int hauFils1 = (fils1 != null && fils1.length > 0 && fils1[0] != null) ? fils1[0].length : 0;

                int larFils2 = (fils2 != null) ? fils2.length : 0;
                int hauFils2 = (fils2 != null && fils2.length > 0 && fils2[0] != null) ? fils2[0].length : 0;

                int larFils3 = (fils3 != null) ? fils3.length : 0;
                int hauFils3 = (fils3 != null && fils3.length > 0 && fils3[0] != null) ? fils3[0].length : 0;

                Color[][] mat = new Color[larFils0+larFils1][hauFils0+hauFils2];


                //System.out.println("Création tableau");

                // Copie du tableau 0
                
                for (int i = 0; i < larFils0; ++i) {
                    for (int j = 0; j < hauFils0; ++j) {
                        mat[i][j] = fils0[i][j];
                    }
                }

                // Copie du tableau 1
                for (int i = 0; i < larFils1; ++i) {
                    for (int j = 0; j < hauFils1; ++j) {
                        mat[larFils0 + i][j] = fils1[i][j];
                    }
                }

                // Copie du tableau 2
                for (int i = 0; i < larFils2; ++i) {
                    for (int j = 0; j < hauFils2; ++j) {
                        mat[i][hauFils0 + j] = fils2[i][j];
                    }
                }

                // Copie du tableau 3
                for (int i = 0; i < larFils3; ++i) {
                    for (int j = 0; j < hauFils3; ++j) {
                        mat[larFils0 + i][hauFils0 + j] = fils3[i][j];
                    }
                }

                return mat;

            } else if (current.getValeur().getClass() == Color.class) {
                // Si on est arrivé à une feuille, on en renvoie la valeur.


                if (diffProf == 0) {
                    Color[][] c = new Color[1][1];
                    c[0][0] = this.getOtm().recupererCouleur(current.getValeur());
//                    //System.out.println("Renvoi simple valeur");
                    return c;
                } else {
                    // Ici, on doit renvoyer un tableau remplit de la couleur voulue.
                    //System.out.println("diffProf = "+diffProf);
                    int cote = (int) Math.floor(Math.pow(2, diffProf));
                    //System.out.println("cote = "+cote);
                    Color[][] c = new Color[cote][cote];



                    Color coul = this.getOtm().recupererCouleur(current.getValeur());

                    for (int i = 0; i < c.length; ++i) {
                        for (int j = 0; j < c[0].length; ++j) {
                            c[i][j] = coul;
                        }
                    }
//Main.printTableau(c);
                    return c;
                }
            } else {
                return null;
            }//Fin current.getValeur().getClass == couleur

        }//Fin current != null

    }

    /**
     * Ressort le tableau des pixels de l'image traitée.
     * @return Le tableau représentant l'image
     */
    public Color[][] exportBitMapTable() {

        //System.out.println("Début export");
        Color[][] sortie = new Color[this.largeur][this.hauteur];
        
        try {
            //System.out.println("profondeur = "+this.profondeur);

            sortie = this.boutDeTableau(this.qt.getRacine(), 0);

        } catch (PlaceIndisponibleException ex) {
            Logger.getLogger(QuadTreeManager.class.getName()).log(Level.SEVERE, null, ex);
        }

        return sortie;
    }
}
